﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OverStore.Configuration;
using OverStore.Configuration.Components;
using System.Data.SqlServerCe;
using OverStore.Runtime.Storage;
using System.Data;
using OverStore.Configuration.Storage;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OverStore.Exceptions;
using OverStore.Configuration.Builder;
using OverStore.Runtime.ObjectRepository.CallbackManager;
using System.Reflection;
using OverStore.Configuration.Builder.Fluent;
using OverStore.Configuration.ObjectRepository.CommandSet;

namespace OverStore.TutorialAsTest.Tutorial2.Lesson6
{
    [TestClass]
    public class Lesson6
    {
        /********************************
         * Create data storage using following DDL:
         * 
         *  CREATE TABLE AttachTestTable
         *   (	
	     *       Id int primary key not null identity (1,1),
	     *       Name nvarchar(150) not null,
	     *       CreatedAt datetime not null,
	     *       RefId int
         *   );
         *
         *   ALTER TABLE AttachTestTable
         *   ADD CONSTRAINT FK_AttachTestTable_AttachTestTable FOREIGN KEY (RefId)
         *   REFERENCES AttachTestTable (Id);
         *
         *   INSERT INTO AttachTestTable(Name, CreatedAt, RefId)
         *   VALUES ('ValueOne', '2009-10-01', null);
         *
         *   INSERT INTO AttachTestTable(Name, CreatedAt, RefId)
         *   VALUES ('ValueSubOne', '2009-10-03', 1);
         *
         *   INSERT INTO AttachTestTable(Name, CreatedAt, RefId)
         *   VALUES ('ValueSubTwo', '2009-10-01', 2);
         * 
         *******************************/

        class AttachTestClass
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public DateTime CreatedAt { get; set; }
            public int? RefId { get; set; }

            public override string ToString()
            {
                return String.Format("{0} [{1}]", Name, RefId);
            }
        }

        public static OverStoreConfiguration CreateConfig_AttachTestClass(string connectionString)
        {
            return OverStoreFluent.Configure()
                .LogToConsole()
                .ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<AttachTestClass, int>()
                    .WithDepot(Component.Depot.Delegate(
                                    () => new AttachTestClass(),
                                    (instance, identifier) => { instance.Id = identifier; },
                                    Component.IdentifierGenerator.Seq<AttachTestClass>(-1, -1)))
                    .WithMapper(Component.Mapper.Delegate<AttachTestClass>((instance, name, provider) =>
                            {
                                var sc = StringComparer.OrdinalIgnoreCase;
                                if (sc.Equals("Id", name))
                                    return instance.Id;
                                if (sc.Equals("Name", name))
                                    return instance.Name;
                                if (sc.Equals("CreatedAt", name))
                                    return instance.CreatedAt;
                                if (sc.Equals("RefId", name))
                                    return instance.RefId;
                                if (sc.Equals("orig__oid", name))
                                    return provider.GetIdentifier<AttachTestClass, int>(instance);
                                return Missing.Value;
                            },
                            (data, instance, provider) =>
                            {
                                instance.CreatedAt = data.GetDateTime("CreatedAt");
                                instance.Name = data.GetString("Name");
                                instance.RefId = data.GetNullableInt32("RefId");

                                if (data.ContainsName("Id"))
                                    instance.Id = data.GetInt32("Id");
                                if (data.ContainsName("last__id"))
                                    provider.PutNewIdentifier(instance, (int)data.GetDecimal("last__id"));
                                else
                                    if (data.ContainsName("orig__oid"))
                                        provider.PutNewIdentifier(instance, data.GetInt32("orig__oid"));
                            }))
                    .CommandSet()
                        .SelectCommand()
                            .UseSqlCommand("select * from AttachTestTable where id = @orig__oid")
                            .InputParameter("orig__oid", DbType.Int32)
                        .EndCommand()
                        .InsertCommand()
                            .UseSqlCommand("insert into AttachTestTable(Name, CreatedAt, RefId) Values (@Name, @CreatedAt, @RefId)")
                            .InputParameter("Name", DbType.String)
                            .InputParameter( "CreatedAt", DbType.DateTime)
                            .NullableInputParameter("RefId", DbType.Int32)
                            .ThenExecute(ResultComposition.MergePreferSecond)
                                .ExecuteAsReader()
                                .UseSqlCommand("select @@Identity as last__Id")
                            .EndCommand()
                        .UpdateCommand()
                            .UseSqlCommand( "update attachTestTable set name = @name, createdAt = @createdAt, RefId = @RefId where id = @orig__oid")
                            .InputParameter("Name", DbType.String)
                            .InputParameter("CreatedAt", DbType.DateTime)
                            .NullableInputParameter("RefID", DbType.Int32)
                            .InputParameter("orig__oid", DbType.Int32)
                        .EndCommand()
                        .DeleteCommand()
                            .UseSqlCommand("delete from AttachTestTable where id = @orig__oid")
                            .InputParameter("orig__oid", DbType.Int32)
                        .EndCommand()
                    .EndCommandSet()
                    .WithChangesTracker(Component.InstanceChangesTracker.Delegate<AttachTestClass, int, int>(instance =>
                                {
                                    unchecked
                                    {
                                        return instance.CreatedAt.GetHashCode() + instance.Id + (instance.Name ?? String.Empty).GetHashCode()
                                            + (instance.RefId ?? 0);
                                    }
                                }))
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .ReadCommand()
                            .UseSqlCommand("select * from AttachTestTable")
                            .EndCommand()
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<int>(r => r.GetInt32("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        #region Instance Attaching

        /// <summary>
        /// Before start attaching tests, performs few test for sure configuration is valid.
        /// </summary>
        [TestMethod]
        public void AttachTestClass_Read()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var valueOne = session.GetByIdentifier<AttachTestClass, int>(1);
                Assert.AreEqual(1, valueOne.Id);
                Assert.AreEqual("ValueOne", valueOne.Name);
                Assert.AreNotEqual(default(DateTime), valueOne.CreatedAt);
                Assert.IsNull(valueOne.RefId);
            }
        }

        [TestMethod]
        public void AttachTestClass_Update()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var valueOne = session.GetByIdentifier<AttachTestClass, int>(1);
                valueOne.Id = 8;
                valueOne.Name = "VALUE ONE!!!";
                session.SaveAll();
                Assert.AreEqual(1, valueOne.Id);
            }
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var valueOne = session.GetByIdentifier<AttachTestClass, int>(1);
                Assert.AreEqual(1, valueOne.Id);
                Assert.AreEqual("VALUE ONE!!!", valueOne.Name);
            }
        }

        [TestMethod]
        public void AttachTestClass_Insert()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int id1 = 0;
            int id2 = 0;
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newSubOne = new AttachTestClass { CreatedAt = DateTime.Now, Name = "testitem", RefId = 1 };
                session.Add(newSubOne);
                var newSubTwo = new AttachTestClass { CreatedAt = DateTime.Now.AddDays(5), Name = "laterItem", RefId = 3 };
                session.Add(newSubTwo);
                session.SaveAll();

                id1 = newSubOne.Id;
                id2 = newSubTwo.Id;
            }

            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newSub1 = session.GetByIdentifier<AttachTestClass, int>(id1);
                var newSub2 = session.GetByIdentifier<AttachTestClass, int>(id2);


                Assert.AreEqual(id1, newSub1.Id);
                Assert.AreEqual("testitem", newSub1.Name);
                Assert.AreEqual(1, newSub1.RefId);

                Assert.AreEqual(id2, newSub2.Id);
                Assert.AreEqual("laterItem", newSub2.Name);
                Assert.AreEqual(3, newSub2.RefId);
            }
        }

        [TestMethod]
        public void AttachTestClass_AttachExistingInstance()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var existing = session.GetByIdentifier<AttachTestClass, int>(1);
                var actual = session.Attach(existing);
                Assert.AreSame(existing, actual);
            }
        }

        [TestMethod]
        public void AttachTestClass_AttachNewInstance()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int actualId = 0;
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newInstance = new AttachTestClass() { Name = "newinstance", RefId = 1, CreatedAt = DateTime.Now };
                var actual = session.Attach(newInstance);
                Assert.AreSame(newInstance, actual);
                Assert.AreEqual(-2, actual.Id);
                session.SaveAll();
                actualId = actual.Id;
            }
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var existing = session.GetByIdentifier<AttachTestClass, int>(actualId);
                Assert.AreEqual(actualId, existing.Id);
                Assert.AreEqual("newinstance", existing.Name);
                Assert.AreEqual(1, existing.RefId);
            }
        }

        [TestMethod]
        public void AttachTestClass_AttachChangedCopy()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var existing = session.GetByIdentifier<AttachTestClass, int>(3); // Make sure that instance with id = 3 is in session
                var copy = new AttachTestClass { Id = 3, RefId = null, Name = "changedname", CreatedAt = new DateTime(2009, 2, 12) };

                var actual = session.Attach(copy);
                Assert.AreSame(existing, actual); // The existing instance must be used as actual instance.
                Assert.AreEqual("changedname", actual.Name);
                Assert.AreEqual(new DateTime(2009, 2, 12), actual.CreatedAt);
                Assert.IsNull(actual.RefId);
                session.SaveAll();
            }
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var actual = session.GetByIdentifier<AttachTestClass, int>(3);
                Assert.AreEqual("changedname", actual.Name);
                Assert.AreEqual(new DateTime(2009, 2, 12), actual.CreatedAt);
                Assert.IsNull(actual.RefId);
            }
        }

        [TestMethod]
        public void AttachTestClass_AttachDeletedItems()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int identifier = 0;
            // Arrange
            // Create new instance for operate with.
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newItem = new AttachTestClass { Name = "fordelete", CreatedAt = DateTime.Now, RefId = 1 };
                session.Save(newItem);
                identifier = newItem.Id;
            }
            // Act
            // Obtain new item, delete it and attach item the same as deleted
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newItem = session.GetByIdentifier<AttachTestClass, int>(identifier);
                // Just some checks for sure we've obtained correct item
                Assert.AreEqual(identifier, newItem.Id);
                Assert.AreEqual("fordelete", newItem.Name);
                Assert.AreEqual(1, newItem.RefId);

                session.MarkAsDeleted(newItem);

                var copyItem = new AttachTestClass { Id = identifier, Name = "changed", RefId = null, CreatedAt = DateTime.Now.AddYears(-1) };
                var actualInstance = session.Attach(copyItem);

                // Assert
                // Make sure data are copied
                Assert.AreSame(newItem, actualInstance);
                Assert.AreEqual("changed", actualInstance.Name);
                Assert.IsNull(actualInstance.RefId);

                // Save all changes. The actualInstance must be deleted.
                session.SaveAll();
            }
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var throwException = session.GetByIdentifier<AttachTestClass, int>(identifier);
            }
        }

        [TestMethod]
        public void AttachTestClass_AttachDeletedItemsWithClearDeletionMark()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int identifier = 0;
            // Arrange
            // Create new instance for operate with.
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newItem = new AttachTestClass { Name = "fordelete", CreatedAt = DateTime.Now, RefId = 1 };
                session.Save(newItem);
                identifier = newItem.Id;
            }

            // Act
            // Obtain new item, delete it and attach item the same as deleted
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var newItem = session.GetByIdentifier<AttachTestClass, int>(identifier);
                // Just some checks for sure we've obtained correct item
                Assert.AreEqual(identifier, newItem.Id);
                Assert.AreEqual("fordelete", newItem.Name);
                Assert.AreEqual(1, newItem.RefId);

                session.MarkAsDeleted(newItem);

                var copyItem = new AttachTestClass { Id = identifier, Name = "changed", RefId = null, CreatedAt = DateTime.Now.AddYears(-3) };
                var actualInstance = session.Attach(copyItem);

                // Assert
                // Make sure data are copied
                Assert.AreSame(newItem, actualInstance);
                Assert.AreEqual("changed", actualInstance.Name);
                Assert.IsNull(actualInstance.RefId);

                // Save all changes. The actualInstance must be deleted.
                session.SaveAll();
            }
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                // Instance should not be deleted.
                var existingInstance = session.GetByIdentifier<AttachTestClass, int>(identifier);
                Assert.AreEqual("changed", existingInstance.Name);
                Assert.IsNull(existingInstance.RefId);
            }
        }

        #endregion

        class AttachTestRef
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public DateTime CreatedAt { get; set; }
            public AttachTestRef Reference { get; set; }

            public override string ToString()
            {
                // return String.Format("{0} - {1} [{2}]", Id, Name, Reference == null ? default(object) : Reference.Id);
                return Name;
            }
        }

        public static OverStoreConfiguration CreateConfig_AttachTestRef(string connectionString)
        {
            return OverStoreFluent.Configure()
                .LogToConsole()
                .ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<AttachTestRef, int>()
                .WithCallback(
                    Component.CallbackManager.Delegate<AttachTestRef>().AddCallback((instance, operation, provider) =>
                        {
                            if (instance.Reference != null)
                            {
                                if (operation == PersistenceOperation.Saving)
                                    provider.Save(instance.Reference);
                            }
                            return true;
                        }))
                .EagerLoading()
                .WithDepot(Component.Depot.Delegate(
                                () => new AttachTestRef(),
                                (instance, identifier) => { instance.Id = identifier; },
                                Component.IdentifierGenerator.Seq<AttachTestRef>(-1, -1)))
                .WithMapper(Component.Mapper.Delegate<AttachTestRef>((instance, name, provider) =>
                        {
                            var sc = StringComparer.OrdinalIgnoreCase;
                            if (sc.Equals("Id", name))
                                return instance.Id;
                            if (sc.Equals("Name", name))
                                return instance.Name;
                            if (sc.Equals("CreatedAt", name))
                                return instance.CreatedAt;
                            if (sc.Equals("RefId", name))
                                return instance.Reference == null ? new int?() : provider.GetIdentifier<AttachTestRef, int>(instance.Reference);
                            if (sc.Equals("orig__oid", name))
                                return provider.GetIdentifier<AttachTestRef, int>(instance);
                            return Missing.Value;
                        },
                        (data, instance, provider) =>
                        {
                            instance.CreatedAt = data.GetDateTime("CreatedAt");
                            instance.Name = data.GetString("Name");
                            var id = data.GetNullableInt32("RefId");
                            instance.Reference = id == null ? default(AttachTestRef) : provider.GetInstance<AttachTestRef, int>(id.Value);
                            if (data.ContainsName("Id"))
                                instance.Id = data.GetInt32("Id");
                            if (data.ContainsName("last__id"))
                                provider.PutNewIdentifier(instance, (int)data.GetDecimal("last__id"));
                            else
                                if (data.ContainsName("orig__oid"))
                                    provider.PutNewIdentifier(instance, data.GetInt32("orig__oid"));
                        }))
                 .WithCommands(new CommandSetConfiguration
                         {
                             SelectCommand = new AdoNetCommandConfiguration
                             {
                                 Sql = "select * from AttachTestTable where id = @orig__oid",
                                 Parameters = new[] { new AdoNetParameter("orig__oid", false, DbType.Int32) },
                                 CommandType = CommandType.Text,
                                 ResultType = AdoCommandResultType.DataReaderResultSet
                             },
                             InsertCommand = new AdoNetScriptConfiguration
                             {
                                 FirstCommand = new AdoNetCommandConfiguration
                                 {
                                     Sql = "insert into AttachTestTable(Name, CreatedAt, RefId) Values (@Name, @CreatedAt, @RefId)",
                                     Parameters = new[] {  new AdoNetParameter { Name = "Name", DataType = DbType.String },
                                                            new AdoNetParameter { Name = "CreatedAt", DataType = DbType.DateTime },
                                                            new AdoNetParameter { Name = "RefId", DataType = DbType.Int32, AllowNull = true } },
                                     ResultType = AdoCommandResultType.OutputParameters,
                                     CommandType = CommandType.Text
                                 },
                                 SecondCommand = new AdoNetCommandConfiguration
                                 {
                                     Sql = "select @@Identity as last__Id",
                                     ResultType = AdoCommandResultType.DataReaderResultSet,
                                     CommandType = CommandType.Text
                                 },
                                 ResultComposition = ResultComposition.MergePreferSecond
                             },
                             UpdateCommand = new AdoNetCommandConfiguration
                             {
                                 Sql = "update attachTestTable set name = @name, createdAt = @createdAt, RefId = @RefId where id = @orig__oid",
                                 Parameters = new[] { new AdoNetParameter { Name = "Name", DataType = DbType.String },
                                                       new AdoNetParameter { Name = "CreatedAt", DataType = DbType.DateTime },
                                                       new AdoNetParameter { Name = "RefID", DataType = DbType.Int32, AllowNull = true },
                                                       new AdoNetParameter { Name = "orig__oid", DataType = DbType.Int32 } },
                                 CommandType = CommandType.Text
                             },
                             DeleteCommand = new AdoNetCommandConfiguration
                             {
                                 Sql = "delete from AttachTestTable where id = @orig__oid",
                                 Parameters = new[] { new AdoNetParameter { Name = "orig__oid", DataType = DbType.Int32 } },
                                 CommandType = CommandType.Text,
                                 ResultType = AdoCommandResultType.OutputParameters
                             }

                         })
                 .WithChangesTracker(Component.InstanceChangesTracker.Delegate<AttachTestRef, int, int>(instance =>
                         {
                             unchecked
                             {
                                 return instance.CreatedAt.GetHashCode() + instance.Id + (instance.Name ?? String.Empty).GetHashCode()
                                     + (instance.Reference == null ? 0 : instance.Reference.GetHashCode());
                             }
                         }))
                 .NoErrorTransforming()
                 .ByIdentifierAttaching(i => i.Id)
                .EndObjectRepository()
            .End();
        }

        #region Attaching instance with references

        /// <summary>
        /// Before start attaching tests, performs few test for sure configuration is valid.
        /// </summary>
        [TestMethod]
        public void AttachTestRef_Read()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var valueSubTwo = session.GetByIdentifier<AttachTestRef, int>(2);
                Assert.AreEqual(2, valueSubTwo.Id);
                Assert.IsNotNull(valueSubTwo.Name);
                Assert.AreNotEqual(default(DateTime), valueSubTwo.CreatedAt);
                Assert.IsNotNull(valueSubTwo.Reference);

                var valueOne = session.GetByIdentifier<AttachTestRef, int>(1);
                Assert.AreSame(valueOne, valueSubTwo.Reference);
            }
        }

        [TestMethod]
        public void AttachTestRef_Update()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var valueOne = session.GetByIdentifier<AttachTestRef, int>(1);
                valueOne.Id = 8;
                valueOne.Name = "VALUE ONE!!!";
                valueOne.Reference = session.GetByIdentifier<AttachTestRef, int>(2);
                valueOne.Reference.Reference = valueOne;
                session.SaveAll();
                Assert.AreEqual(1, valueOne.Id);
            }
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var valueOne = session.GetByIdentifier<AttachTestRef, int>(1);
                Assert.AreEqual(1, valueOne.Id);
                Assert.AreEqual("VALUE ONE!!!", valueOne.Name);
                Assert.AreSame(valueOne.Reference.Reference, valueOne);
            }
        }

        [TestMethod]
        public void AttachTestRef_Insert()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int id1 = 0;
            int id2 = 0;
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var newSubOne = new AttachTestRef
                {
                    CreatedAt = DateTime.Now,
                    Name = "testitem",
                    Reference = session.GetByIdentifier<AttachTestRef, int>(3)
                };
                session.Add(newSubOne);
                var newSubTwo = new AttachTestRef { CreatedAt = DateTime.Now.AddDays(5), Name = "laterItem" };
                session.Add(newSubTwo);
                session.SaveAll();

                id1 = newSubOne.Id;
                id2 = newSubTwo.Id;
            }

            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var newSub1 = session.GetByIdentifier<AttachTestRef, int>(id1);
                var newSub2 = session.GetByIdentifier<AttachTestRef, int>(id2);

                Assert.AreEqual(id1, newSub1.Id);
                Assert.AreEqual("testitem", newSub1.Name);
                Assert.AreSame(session.GetByIdentifier<AttachTestRef, int>(3), newSub1.Reference);

                Assert.AreEqual(id2, newSub2.Id);
                Assert.AreEqual("laterItem", newSub2.Name);
                Assert.IsNull(newSub2.Reference);
            }
        }

        [TestMethod]
        public void AttachTestRef_InsertComplexGraph()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int parentId;
            int childId;
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var newItem = new AttachTestRef
                {
                    Name = "parent",
                    CreatedAt = DateTime.Now,
                    Reference = new AttachTestRef
                    {
                        Name = "child",
                        CreatedAt = DateTime.Now
                    }
                };

                session.Save(newItem);
                newItem.Reference.Reference = newItem;
                session.Save(newItem);

                parentId = newItem.Id;
                childId = newItem.Reference.Id;
            }
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                var parent = session.GetByIdentifier<AttachTestRef, int>(parentId);
                var child = session.GetByIdentifier<AttachTestRef, int>(childId);

                // Check hierarchy.
                Assert.AreSame(child, parent.Reference);
                Assert.AreSame(parent, parent.Reference.Reference);

                Assert.AreEqual(parentId, parent.Id);
                Assert.AreEqual("parent", parent.Name);

                Assert.AreEqual(childId, child.Id);
                Assert.AreEqual("child", child.Name);
            }
        }

        [TestMethod]
        public void AttachTestRef_AttachExistingInstance()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                // Arrange
                // 
                // make sure that instance with id = 1 exists in session.
                var one = session.GetByIdentifier<AttachTestRef, int>(1);

                var now = DateTime.Now;
                var copy = new AttachTestRef
                {
                    Id = 2,
                    Name = "name",
                    CreatedAt = DateTime.Now,
                    Reference = new AttachTestRef
                    {
                        Id = 1,
                        Name = "copy_one",
                        CreatedAt = now
                    }
                };

                // Act
                var actual = session.Attach(copy);

                // Assert
                Assert.AreSame(one, actual.Reference);
                Assert.AreEqual("copy_one", one.Name);
                Assert.AreEqual(now, one.CreatedAt);
            }
        }

        [TestMethod]
        public void AttachTestRef_AttachExistingInstanceSameRef()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                // Arrange
                // 
                // make sure that instance with id = 1 exists in session.
                var one = session.GetByIdentifier<AttachTestRef, int>(1);

                var now = DateTime.Now;
                var copy = new AttachTestRef
                {
                    Id = 1,
                    Name = "name",
                    CreatedAt = DateTime.Now,
                    Reference = new AttachTestRef
                    {
                        Id = 1,
                        Name = "copy_one",
                        CreatedAt = now
                    }
                };

                // Act
                var actual = session.Attach(copy);

                // Assert
                Assert.AreSame(one, actual.Reference);
                Assert.AreEqual("copy_one", one.Name);
                Assert.AreEqual(now, one.CreatedAt);
                Assert.AreSame(actual, actual.Reference);
            }
        }

        [TestMethod]
        public void AttachTestRef_AttachExistingInstanceComplexRefGraph()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestRef(connectionString).CreateSession())
            {
                // Arrange
                // 
                // make sure that instance with id = 1 exists in session.
                var one = session.GetByIdentifier<AttachTestRef, int>(1);

                var copy = new AttachTestRef
                {
                    Id = 2,
                    Name = "name",
                    CreatedAt = DateTime.Now
                };

                copy.Reference = new AttachTestRef
                {
                    Id = 3,
                    Name = "three",
                    CreatedAt = DateTime.Now,
                    Reference = one
                };

                // Act
                var actual = session.Attach(copy);

                // Assert
                var three = session.GetByIdentifier<AttachTestRef, int>(3);

                Assert.AreSame(three.Reference, one);
                Assert.AreSame(actual.Reference, three);


                Assert.AreEqual(2, actual.Id);
                Assert.AreEqual("name", copy.Name);
            }
        }

        #endregion

        #region Collection Attaching

        /// <summary>
        /// Before start collection attaching test, sure that configuration is correct.
        /// </summary>
        [TestMethod]
        public void CollectionAttach_LoadCollection()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var list = new List<AttachTestClass>();
                session.ReloadCollection(list);

                Assert.IsTrue(list.Count >= 3);

                var one = list.Single(i => i.Id == 1);
                Assert.AreSame(session.GetByIdentifier<AttachTestClass, int>(1), one);

                Assert.AreEqual(1, one.Id);
                Assert.AreEqual("ValueOne", one.Name);
                Assert.IsNull(one.RefId);

                var two = list.Single(i => i.Id == 2);
                Assert.AreSame(session.GetByIdentifier<AttachTestClass, int>(2), two);

                Assert.AreEqual(2, two.Id);
                Assert.IsNotNull(two.Name);
                Assert.AreEqual(1, two.RefId);

                var three = list.Single(i => i.Id == 3);
                Assert.AreSame(session.GetByIdentifier<AttachTestClass, int>(3), three);

                Assert.AreEqual(3, three.Id);
                Assert.IsNotNull(three.Name);
            }
        }

        /// <summary>
        /// Test on attaching loaded collection.
        /// </summary>
        [TestMethod]
        public void CollectionAttach_SameCollection()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            int count = 0;
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var list = new List<AttachTestClass>();
                session.ReloadCollection(list);

                var content = list.ToList();

                count = list.Count;
                var result = list.Select(i => session.Attach(i)).ToList();
                // Instance content is not changed.
                CollectionAssert.AreEqual(content, result);
            }
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var list = new List<AttachTestClass>();
                session.ReloadCollection(list);
                Assert.AreEqual(count, list.Count);
            }
        }

        /// <summary>
        /// Test of attaching collection with existing but unregistered instances.
        /// </summary>
        [TestMethod]
        public void CollectionAttach_ExistingItems()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var one = session.GetByIdentifier<AttachTestClass, int>(1);
                var two = session.GetByIdentifier<AttachTestClass, int>(2);
                var collection = new List<AttachTestClass>
                {
                    new AttachTestClass { Id = 1, Name = "one", CreatedAt = DateTime.Now },
                    new AttachTestClass { Id = 2, Name = "two", CreatedAt = DateTime.Now, RefId = 1 },
                    new AttachTestClass { Id = 3, Name = "three", CreatedAt = DateTime.Now, RefId = 1 }
                };

                var result = collection.Select(i => session.Attach(i)).ToList();

                // Attached collection must have the same element count.
                Assert.AreEqual(collection.Count, result.Count);

                // Existing elements must be updated.
                Assert.AreEqual("one", one.Name);
                Assert.AreEqual(1, two.RefId);
                // The element is not existed in session cache before also must be attached correctly.
                var three = session.GetByIdentifier<AttachTestClass, int>(3);
                Assert.AreEqual("three", three.Name);
                Assert.AreEqual(1, three.RefId);
            }
        }

        [TestMethod]
        public void CollectionAttach_NewItems()
        {
            var connectionString = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            using (var session = CreateConfig_AttachTestClass(connectionString).CreateSession())
            {
                var collection = new List<AttachTestClass>
                {
                    new AttachTestClass {         Name = "new", CreatedAt = new DateTime(2009, 1, 1), RefId = 1 },
                    new AttachTestClass { Id = 1, Name = "one", CreatedAt = DateTime.Now },
                    new AttachTestClass { Id = 2, Name = "two", CreatedAt = DateTime.Now, RefId = 1 },
                    new AttachTestClass { Id = 3, Name = "three", CreatedAt = DateTime.Now, RefId = 1 },
                };

                var result = collection.Select(i => session.Attach(i)).ToList();

                // Attached collection must have the same element count.
                Assert.AreEqual(collection.Count, result.Count);

                var first = result.First();

                Assert.AreNotEqual(0, first.Id);
                Assert.AreEqual("new", first.Name);
                Assert.AreEqual(new DateTime(2009, 1, 1), first.CreatedAt);
                Assert.AreEqual(1, first.RefId);

                Assert.AreSame(first, session.GetByIdentifier<AttachTestClass, int>(first.Id));


                var one = session.GetByIdentifier<AttachTestClass, int>(1);
                var two = session.GetByIdentifier<AttachTestClass, int>(2);
                // Existing elements must be updated.
                Assert.AreEqual("one", one.Name);
                Assert.AreEqual(1, two.RefId);
                // The identifier and index are fully unrelated things.
                Assert.AreSame(one, result[1]);
                Assert.AreSame(two, result[2]);
                // The element is not existed in session cache before also must be attached correctly.
                var three = session.GetByIdentifier<AttachTestClass, int>(3);
                Assert.AreEqual("three", three.Name);
                Assert.AreEqual(1, three.RefId);
            }
        }

        #endregion

        #region Attaching with generated keys

        public class CustomAttachingEntity
        {
            public Guid Id { get; set; }

            public string Name { get; set; }

            public bool IsNew { get; set; }

            public bool IsModified { get; set; }
        }

        public static OverStoreConfiguration CreateConfigCustomAttaching(string connectionString)
        {
            return OverStoreFluent.Configure()
                    .LogToConsole()
                    .ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                    .MapClassToTable<CustomAttachingEntity, Guid>("CustomAttachingEntity")
                        .MapKey("Id", e => e.Id, (e, v) => e.Id = v, Component.IdentifierGenerator.Guid<CustomAttachingEntity>())
                        .MapScalarValue("Name", e => e.Name, (e, v) => e.Name = v)
                        .ConfigureAttacher()
                            .ExtractIdentifier(e => e.Id)
                            .GenerateNewIdentifierIf(e => e.Id == default(Guid))
                            .InstanceIsNewIf(e => e.IsNew || e.Id == default(Guid))
                            .DataChangedIf(e => e.IsModified)
                        .End()
                    .EndTableMapping()
                .End();
        }

        [TestMethod]
        public void CustomAttaching_AttachNew()
        {
            var cs = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var id = Guid.NewGuid();
            // Creating new instance by setting IsNew flag
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = new CustomAttachingEntity
                {
                    Id = id,
                    IsModified = false,
                    IsNew = true,
                    Name = "Created by setting IsNew flag"
                };
                var attached = session.Attach(entity);
                session.SaveAll();
            }
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = session.GetByIdentifier<CustomAttachingEntity, Guid>(id);
                Assert.AreEqual(id, entity.Id);
                Assert.AreEqual("Created by setting IsNew flag", entity.Name);
            }
        }

        [TestMethod]
        public void CustomAttaching_AttachWithDefaultIdentifier()
        {
            var cs = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var id = Guid.Empty;
            // Creating new instance by providing empty identifier.
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = new CustomAttachingEntity
                {
                    IsModified = false,
                    IsNew = false,
                    Name = "Created by setting IsNew flag"
                };
                var attached = session.Attach(entity);
                Assert.AreNotEqual(id, attached.Id);
                id = attached.Id;
                session.SaveAll();
            }
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = session.GetByIdentifier<CustomAttachingEntity, Guid>(id);
                Assert.AreEqual(id, entity.Id);
                Assert.AreEqual("Created by setting IsNew flag", entity.Name);
            }
        }

        [TestMethod]
        public void CustomAttaching_AttachExistingNotModified()
        {
            var cs = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var id = new Guid("1af5eaf9-51a4-43f0-90c3-32901afc39b6");
            // Attaching existing instance with IsModified flag sets to false.
            // Instance must not be saved.
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = new CustomAttachingEntity
                {
                    Id = id,
                    IsModified = false,
                    IsNew = false,
                    Name = "Modified but must not be saved"
                };
                var attached = session.Attach(entity);
                session.SaveAll();
            }
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = session.GetByIdentifier<CustomAttachingEntity, Guid>(id);
                Assert.AreEqual(id, entity.Id);
                Assert.AreEqual("Entity1", entity.Name);
            }
        }

        [TestMethod]
        public void CustomAttaching_AttachExistingModified()
        {
            var cs = TestHelper.CreateTestDbCopyAndBuildConnectionString();
            var id = new Guid("1af5eaf9-51a4-43f0-90c3-32901afc39b6");
            // Attaching existing instance with IsModified flag sets to true.
            // Instance must be saved.
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = new CustomAttachingEntity
                {
                    Id = id,
                    IsModified = true,
                    IsNew = false,
                    Name = "Modified and saved"
                };
                var attached = session.Attach(entity);
                session.SaveAll();
            }
            using (var session = CreateConfigCustomAttaching(cs).CreateSession())
            {
                var entity = session.GetByIdentifier<CustomAttachingEntity, Guid>(id);
                Assert.AreEqual(id, entity.Id);
                Assert.AreEqual("Modified and saved", entity.Name);
            }
        }

        #endregion
    }
}
